﻿using System;
using Nop.Core.Domain.Orders;
using Nop.Core.Plugins;
using Nop.Services.Events;
using Nop.Services.Orders;
using System.Net;
using System.IO;
using System.Text;
using System.Web;
using Nop.Services.Logging;
using System.Globalization;
using Nop.Services.Common;
using Nop.Core.Domain.Customers;
using Nop.Services.Messages;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Nop.Plugin.Widgets.SpreadButton
{
    public class EventConsumer : IConsumer<OrderPaidEvent>
    {
        private readonly HttpContextBase _httpContext;
        private readonly SpreadButtonSettings _spreadButtonSettings;
        private readonly IOrderService _orderService;
        private readonly ILogger _logger;
        private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService;

        private readonly CultureInfo _usCulture;

        public EventConsumer(HttpContextBase httpContext,
            SpreadButtonSettings spreadButtonSettings,
            IOrderService orderService,
            INewsLetterSubscriptionService newsLetterSubscriptionService,
            ILogger logger)
        {
            this._httpContext = httpContext;
            this._spreadButtonSettings = spreadButtonSettings;
            this._orderService = orderService;
            this._logger = logger;
            this._newsLetterSubscriptionService = newsLetterSubscriptionService;
            this._usCulture = new CultureInfo("en-US");
        }

        /// <summary>
        /// Handles the event.
        /// </summary>
        /// <param name="eventMessage">The event message.</param>
        void IConsumer<OrderPaidEvent>.HandleEvent(OrderPaidEvent eventMessage)
        {
            //Make an async call
            var caller = new PublishOrderPaidToSpreadButtonCaller(PublishOrderPaidToSpreadButton);
            //var cb = new AsyncCallback();
            caller.BeginInvoke(eventMessage.Order, null, null);
        }


        // This method serializes the event data into JSON and posts to the callback urls
        private delegate void PublishOrderPaidToSpreadButtonCaller(Order o);
        private void PublishOrderPaidToSpreadButton(Order o)
        {
            //Waiting for the order to be created;
            System.Threading.Thread.Sleep(10000);

            var order = _orderService.GetOrderById(o.Id);

            var contentParts = new Dictionary<string, string>();

            contentParts.Add("data[action]", "order"); // "signup", ou par defaut "order"

            contentParts.Add("data[order_state]", "20"); //Etat de la commande : Payé
            contentParts.Add("data[order_num]", order.Id.ToString()); // Numéro de facture
            contentParts.Add("data[order_amount]", order.OrderTotal.ToString("0.00", _usCulture)); // Montant de la commande
            contentParts.Add("data[order_idorder]", order.Id.ToString()); // Numéro de commande

            contentParts.Add("data[customer_email]", order.BillingAddress.Email); // 
            contentParts.Add("data[customer_name]", order.BillingAddress.LastName); // 
            contentParts.Add("data[customer_firstname]", order.BillingAddress.FirstName); // 
            contentParts.Add("data[customer_country]", order.BillingAddress.Country.TwoLetterIsoCode); //
            var birthday = order.Customer.GetAttribute<DateTime?>(SystemCustomerAttributeNames.DateOfBirth);
            if (birthday.HasValue)
                contentParts.Add("data[customer_birthday]", birthday.Value.ToString(_usCulture)); // 
            var gender = order.Customer.GetAttribute<string>(SystemCustomerAttributeNames.Gender);
            if (!string.IsNullOrEmpty(gender))
                contentParts.Add("data[customer_gender]", gender); // 
            bool isoptin = false;
            if (order.Customer.IsRegistered())
            {
                var subs = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(order.Customer.Email);
                if (subs != null && subs.Active)
                    isoptin = true;
            }
            contentParts.Add("data[customer_isoptin]", isoptin.ToString()); // Accepter les emails

            var sbtCookie = _httpContext.Request.Cookies["sbt"];
            if (sbtCookie != null)
                contentParts.Add("data[customer_cookie]", sbtCookie.Value); // Très important, sinon le tracking ne peux pas se faire. Dand le javascript, l'option "sbsetcookie" doit être à true, c'est ce qui permet de transférer le cookie du nom de domaine SpreadButton au domaine local.


            contentParts.Add("data[customer_num1]", order.Customer.Id.ToString());
            //dic.Add("data[customer_num2]", order.Id); 
            //dic.Add("data[customer_txt1]", order.Id); 
            //dic.Add("data[customer_txt2]", order.Id); 
            //dic.Add("data[customer_date1]", order.Id);
            //dic.Add("data[customer_date2]", order.Id);
            //dic.Add("data[customer_bool1]", order.Id);
            //dic.Add("data[customer_bool2]", order.Id);

            contentParts.Add("public", _spreadButtonSettings.PublicApiKey);
            var postData = GetEncodedContent(contentParts);

            // Création de la signature
            var sign = Sha1LikePHP(postData + _spreadButtonSettings.PrivateApiKey);
            postData += string.Format("&sign={0}", sign);

            TryPostUrlEncodedContent("http://social-sb.com/api/CreateAction", postData);
        }

        // This method serializes the event data into JSON and posts to the callback urls
        private delegate void PublishOrderRefundedToSpreadButtonCaller(Order o);
        private void PublishOrderRefundedToSpreadButton(Order o)
        {
            //Waiting for the order to be created;
            System.Threading.Thread.Sleep(10000);

            var contentParts = new Dictionary<string, string>();

            //Manually encode [ and ] because php and .NET do not use the same casing, and sign function needs exactly the same input
            contentParts.Add("data[order_state]", "20"); //Nouvel état de la commande : Payé
            //dic.Add("data[order_num]", o.Id); // Nouveau numéro de facture
            //dic.Add("data[order_idorder]", o.Id); // Nouveau numéro de commande
            contentParts.Add("public", _spreadButtonSettings.PublicApiKey);
            contentParts.Add("request[order_idorder]", o.Id.ToString()); // Recherche de la commande par l’identifiant de la commande coté marchand
            var postData = GetEncodedContent(contentParts);

            // Création de la signature
            var sign = Sha1LikePHP(postData + _spreadButtonSettings.PrivateApiKey);
            postData += string.Format("&sign={0}", sign);

            TryPostUrlEncodedContent("http://social-sb.com/api/UpdateOrder", postData);
        }


        private bool TryPostUrlEncodedContent(string destinationUrl, string encodedContent)
        {
            try
            {
                var serviceUri = new Uri(destinationUrl);

                var request = (HttpWebRequest)WebRequest.Create(serviceUri);
                request.KeepAlive = true;
                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded";
                Stream requestStream = request.GetRequestStream();

                if (encodedContent != null)
                {
                    byte[] requestBytes = Encoding.UTF8.GetBytes(encodedContent);
                    requestStream.Write(requestBytes, 0, requestBytes.Length);
                }

                requestStream.Close();
                WebResponse response = request.GetResponse();
                StreamReader responseStream = new StreamReader(response.GetResponseStream());
                string responseText = responseStream.ReadToEnd();
                if (responseText.Contains("error"))
                    throw new ApplicationException(responseText);

                return true;
            }
            catch (Exception ex)
            {
                _logger.Error("Error while trying to make http request in SpeadButton plugin", ex);
                return false;
            }
        }

        private string GetEncodedContent(Dictionary<string, string> parts)
        {
            var EncodedContent = new StringBuilder();
            bool firstPart = true;
            foreach (var part in parts)
            {
                EncodedContent.AppendFormat("{0}{1}={2}", firstPart == true ? "" : "&", UrlEncodeLikePHP(part.Key), UrlEncodeLikePHP(part.Value));
                firstPart = false;
            }

            return EncodedContent.ToString();
        }

        #region Utilities for PHP compatibility

        //Uppercase url encode, that is not a good pratcice as RFCs say that you can use upper or lower case for escaped characters in the URL
        //But SP has signed the encoded version of the url, so we need to use the same casing :(
        private static string UrlEncodeLikePHP(string url)
        {
            char[] encodedUrl = HttpUtility.UrlEncode(url).ToCharArray();
            for (int i = 0; i < encodedUrl.Length - 2; i++)
            {
                if (encodedUrl[i] == '%')
                {
                    encodedUrl[i + 1] = char.ToUpper(encodedUrl[i + 1]);
                    encodedUrl[i + 2] = char.ToUpper(encodedUrl[i + 2]);
                }
            }
            return new string(encodedUrl);
        }

        //Keep in mind this function but is slower than the other one
        //private static string UrlEncodeLikePHP(string url)
        //{
        //    url = HttpUtility.UrlEncode(url);
        //    return Regex.Replace(url, "(%[0-9a-f][0-9a-f])", c => c.Value.ToUpper());
        //}

        private static string Sha1LikePHP(string clearText)
        {
            //ASCII encoding is needed to be compliant with php sha1 function
            var clearBytes = new System.Text.ASCIIEncoding().GetBytes(clearText);
            System.Security.Cryptography.SHA1Managed SHhash = new System.Security.Cryptography.SHA1Managed();
            var encryptedBytes = SHhash.ComputeHash(clearBytes);
            string encryptedText = "";
            foreach (byte encryptedByte in encryptedBytes)
                encryptedText += String.Format("{0:x2}", encryptedByte);
            return encryptedText;
        }
        #endregion



    }
}